# Based on bazel_rules_hdl/synthesis/synth.tcl.
#
# Tcl script for the synthesize_rtl rule, customized to break each
# XLS pipeline stage into its own module.
#
# It can be replaced by a user-defined script by overriding the synth_tcl
# argument of that rule.

# User-defined synthesis scripts need to consult the following environment
# variables for their parameters:
# FLIST = a file that lists verilog sources (one file per line)
# UHDM_FLIST = a file that lists UHDM sources (one file per line)
# TOP = top module for synthesis
# LIBERTY = liberty file for the target technology library
# OUTPUT = verilog file for synthesis output
# STATS_JSON = json file for structured stats output
# DONT_USE_ARGS = string passed to ABC, e.g.
#                 "-dont_use cell1* -dont_use cell2* <etc>"

yosys -import

# Hack to support the new requirements in bazel_rules_hdl should be fixed by
# a better solution upstream.
if { [info exists ::env(STANDARD_CELL_BLACK_BOX)] } {
  set outfile [open $::env(STANDARD_CELL_BLACK_BOX) w+]
  puts $outfile "empty_file"
  close $outfile
}

# read design
set srcs_flist_path $::env(FLIST)
set srcs_flist_file [open $srcs_flist_path "r"]
set srcs_flist_data [read $srcs_flist_file]
set srcs [split $srcs_flist_data "\n"]
puts $srcs
foreach src $srcs {
    # Skip empty lines, including the implict one after the last \n delimiter
    # for files that end with a newline.
    if {$src eq ""} continue
    if {[info exists ::env(USE_SURELOG_FRONTEND)]} {
      yosys read_systemverilog $src
    } else {
      yosys read_verilog -sv -defer $src
    }
}

# read UHDM designs
set srcs_uhdm_flist_path $::env(UHDM_FLIST)
set srcs_uhdm_flist_file [open $srcs_uhdm_flist_path "r"]
set srcs_uhdm_flist_data [read $srcs_uhdm_flist_file]
set srcs [split $srcs_uhdm_flist_data "\n"]
puts $srcs
foreach src $srcs {
    # Skip empty lines, including the implict one after the last \n delimiter
    # for files that end with a newline.
    if {$src eq ""} continue
    read_uhdm $src
}

# generic synthesis
set top $::env(TOP)
hierarchy -check -top $top
# Move proc_mux at the end of `yosys proc` to avoid inferred latches.
# See https://github.com/YosysHQ/yosys/issues/3456
# Ideally the bug would be solved in UHDM/Yosys.
yosys proc -nomux
yosys proc_mux
yosys flatten

# Remove $print cells.  These cells represent Verilog $display() tasks.
# Some place and route tools cannot handle these in the output Verilog,
# so remove them here.
yosys delete {*/t:$print}

# Give created instances useful names.
# At this stage it is mainly flipflops created by the `proc` # pass.
yosys autoname

yosys synth -top $top

# Give created instances useful names.
# At this stage it is all the other synthesizable constructs.
# This should be done before techmapping where things can be converted
# dramatically and having useful names is helpful for debugging.
yosys autoname

# create module for each XLS stage
set max_stage 20
for {set i 0} {$i < $max_stage} {incr i} {
    log doing sel p${i}
    select -none
    #
    # Documentation of the following select command:
    #
    # select */w:p${i}_*                        Add wires with names pN_*
    # select */w:p${i}_* %ci:+\[Q\]             Add cells that drive those wires using a pin Q (i.e. flops)
    # select */w:p${i}_* %ci:+\[Q\] */w:* %d    Remove wires from active set
    # select */w:p${i}_* %ci:+\[Q\] */w:* %d %ci:+\[D,S,R\]             Add input wires driving flop D/S/R pins
    # select */w:p${i}_* %ci:+\[Q\] */w:* %d %ci:+\[D,S,R\] %cie*       Add fanin cones, stopping at flops
    #    ... and a final */w:* %d to remove wires (we just want cells)
    #
    select */w:p${i}_* %ci:+\[Q\] */w:* %d %ci:+\[D,S,R\] %cie* */w:* %d
    select -count
    set mcount [scratchpad -copy select.count result.string]
    if { $mcount > 0 } {
        log -n Cell count in p${i}mod: $mcount
        submod -name "p${i}mod"
    }
}
select -clear
opt_clean -purge

# ====== mapping to liberty ======
set liberty $::env(LIBERTY)
dfflibmap -liberty $liberty

# custom ABC script including cell resizing
set abc_script "+strash;fraig;scorr;strash;dch,-f;map,-M,1;topo;stime;buffer;topo;stime;minsize;stime;upsize;stime;dnsize;stime"

if { [info exists ::env(CLOCK_PERIOD) ] } {
  abc -liberty $liberty -dff -g aig -D $::env(CLOCK_PERIOD) -script $abc_script {*}$::env(DONT_USE_ARGS)
} else {
  abc -liberty $liberty -dff -g aig -script $abc_script {*}$::env(DONT_USE_ARGS)
}

# Remove internal only aliases for public nets and then give created instances
# useful names. At this stage it is anything generated by the techmapping
# passes.
yosys opt_clean -purge
yosys autoname

# ====== write synthesized design
set output $::env(OUTPUT)
write_verilog $output

# ====== print stats / info ======
select -clear
stat -liberty $liberty
if { [info exists ::env(STATS_JSON) ] } {
  tee -q -o $::env(STATS_JSON) stat -liberty $liberty -json
  yosys log Structured stats: $::env(STATS_JSON)
}
read_liberty -lib -ignore_miss_func $liberty
ltp -noff

yosys log -n "Flop count: "
yosys select -count t:*__df* t:DF* t:*_DFF* t:*_SDFF* t:*_ADFF* t:*dff

# ====== per-module flop count ======
for {set i 0} {$i < $max_stage} {incr i} {
    set mname "p${i}mod"

    # make sure module exists and is nonempty
    select -clear
    select -count ${mname}/*
    set mcount [scratchpad -copy select.count result.string]
    if { $mcount == 0 } { continue }

    yosys select -clear
    yosys log -n Flop count $mname:\ 
    yosys select -module $mname -count t:*__df* t:DF* t:*_DFF* t:*_SDFF* t:*_ADFF* t:*dff
}

set base_liberty [file tail $liberty]
yosys log Liberty: $base_liberty
